﻿using System.Data;
using System.Drawing;
using Home.Haushalt.Lib.Element;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.IO;
using System.Linq;

namespace Home.Haushalt.Lib
{
    /// <summary>
    /// 
    /// </summary>
    public class ElementBase
    {
        #region CONSTANTS

        public static readonly Type ElementType = typeof(ElementBase);

        public const string ELEMENTGROUP_ELEMENT = "Element";
        public const string ELEMENTGROUP_PROPERTY = "Property";
        public const string ELEMENTGROUP_SUPPORT = "Support";

        public const string ELEMENTPROPERTY_ID = "i";
        public const string ELEMENTPROPERTY_LABEL = "k";
        public const string ELEMENTPROPERTY_NOTE = "m";

        #endregion CONSTANTS

        /// <summary></summary>
        public virtual string PropertyFormCaption { get { return ""; } }

        /// <summary></summary>
        public DataRow Row;

        /// <summary></summary>
        protected List<DataRow> _selectedRows = new List<DataRow>();

        /// <summary></summary>
        public int ID { get { return (Row.Table.Columns.Contains(ELEMENTPROPERTY_ID)) ? Row[ELEMENTPROPERTY_ID].ToInt() : -2; } }

        /// <summary></summary>
        public bool IsNew { get { return (ID == -1); } }

        /// <summary></summary>
        public virtual DataTable ElementTable { get { return DAO.Tables[this.GetType()]; } }

        /// <summary></summary>
        public ElementBase ParentElement;

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual ElementBase New()
        {
            Init(DAO.Tables[this.GetType()].NewRow());

            return this;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="row"></param>
        /// <returns></returns>
        public static ElementBase Instantiate(DataRow row)
        {
            return Instantiate(row, new List<DataRow>());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="row"></param>
        /// <param name="selectedRows"></param>
        /// <returns></returns>
        public static ElementBase Instantiate(DataRow row, List<DataRow> selectedRows)
        {
            string elementTypeName = "";
            if (row != null)
                elementTypeName = row.Table.TableName;

            ElementBase element = Instantiate(elementTypeName);

            element.Init(row, selectedRows);

            return element;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementTypeName"></param>
        /// <returns></returns>
        public static ElementBase Instantiate(string elementTypeName)
        {
            ElementBase element = null;

            Type elementType = GetType(elementTypeName);

            if (elementType != null)
            {
                element = Instantiate(elementType);
            }

            return element;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="savableElementTable"></param>
        /// <param name="sourceElementTable"></param>
        internal virtual void FillSavableData(DataTable savableElementTable, DataTable sourceElementTable=null)
        {
            if (sourceElementTable == null)
            {
                sourceElementTable = DAO.Tables[this.GetType()];
            }

            if (sourceElementTable.Columns.Contains(Date.Fields.Id))
            {
                sourceElementTable.Select(string.Format("{0} > -1", Date.Fields.Id)).CopyToDataTable<DataRow>(savableElementTable, LoadOption.OverwriteChanges);
            }
            else
            {
                sourceElementTable.Rows.Cast<DataRow>().CopyToDataTable<DataRow>(savableElementTable, LoadOption.OverwriteChanges);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        internal virtual void AfterOpenElementTable(DataTable elementTable)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementType"></param>
        /// <returns></returns>
        public static ElementBase Instantiate(Type elementType)
        {
            ElementBase element = (ElementBase)Activator.CreateInstance(elementType);

            return element;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementTypeName"></param>
        /// <returns></returns>
        public static Type GetType(string elementTypeName)
        {
            return Type.GetType("Home.Haushalt.Lib.Element." + elementTypeName);
        }

        /// <summary>
        /// 
        /// </summary>
        public ElementBase() { }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parentDataRow"></param>
        public ElementBase(DataRow parentDataRow)
        {
            Init(parentDataRow);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parentDataRow"></param>
        internal virtual void Init(DataRow parentDataRow)
        {
            Init(parentDataRow, new List<DataRow>());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementID"></param>
        internal virtual void Init(int elementID)
        {
            if (this.ElementTable != null)
            {
                DataRow parentDataRow = this.ElementTable.Rows.Cast<DataRow>().Where(item => item[ELEMENTPROPERTY_ID].Equals(elementID)).FirstOrDefault();
                if (parentDataRow != default(DataRow))
                {
                    Init(parentDataRow, new List<DataRow>());
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parentDataRow"></param>
        /// <param name="selectedRows"></param>
        internal virtual void Init(DataRow parentDataRow, List<DataRow> selectedRows)
        {
            if (parentDataRow == null)
            {
                parentDataRow = DAO.Tables[ElementType].NewRow();
            }

            Row = parentDataRow;
            _selectedRows = selectedRows;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual Dictionary<string, object> CalculateUnboundFields() { return new Dictionary<string, object>(); }

        /// <summary>
        /// 
        /// </summary>
        public virtual void PreCalculateUnboundFields() { }

        /// <summary>
        /// 
        /// </summary>
        public virtual void PostCalculateUnboundFields() { }

        /// <summary></summary>
        public virtual string ElementXsd
        {
            get
            {
                //Type t=System.Type.GetType(this.GetType().FullName+"+Fields");

                //string fields = "";
                //if (t != null)
                //{
                //    var a = t.GetFields(BindingFlags.Static | BindingFlags.Public).Where(f => f.Name.StartsWith("unbound", StringComparison.OrdinalIgnoreCase).Equals(false)).Select(item => item.GetValue(null).ToString()).ToList();
                //    foreach (FieldInfo field in t.GetFields(BindingFlags.Static | BindingFlags.Public).Where(f => f.Name.StartsWith("unbound", StringComparison.OrdinalIgnoreCase).Equals(false)))
                //    {
                //       string s= field.GetValue(null).ToString();
                //    }
                //}

                return string.Format(
                    "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
                    "<xs:schema attributeFormDefault=\"unqualified\" elementFormDefault=\"qualified\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">" +
                    "<xs:include schemaLocation=\"{2}\"/>" +
                    "<xs:element name=\"Table\">" +
                    "<xs:complexType>" +
                    "<xs:sequence>" +
                    "<xs:element name=\"{0}\" minOccurs=\"0\" maxOccurs=\"unbounded\"  type=\"{1}\"/>" +
                    "</xs:sequence>" +
                    "</xs:complexType>" +
                    "</xs:element>" +
                    "</xs:schema>", this.GetType().Name, this.GetType().Name + "Element", Path.GetFullPath("elementtypes.xsd"));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columns"></param>
        /// <returns></returns>
        protected bool CheckColumnsInRow(params string[] columns)
        {
            foreach (string column in columns)
            {
                if (!Row.Table.Columns.Contains(column))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        public void SetNextId()
        {
            Row[ELEMENTPROPERTY_ID] = GetNextId();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public int GetNextId()
        {
            return GetNextId(this.GetType());
        }

        /// 
        /// </summary>
        /// <param name="elementType"></param>
        /// <returns></returns>
        public static int GetNextId(Type elementType)
        {
            int nextid = 1;
            if (DAO.Tables[elementType].Rows.Count > 0)
            {
                DAO.Tables[elementType].DefaultView.Sort = ElementBase.ELEMENTPROPERTY_ID + " DESC";
                nextid = DAO.Tables[elementType].DefaultView[0][ElementBase.ELEMENTPROPERTY_ID].ToInt() + 1;
            }

            return nextid;
        }

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="element"></param>
        ///// <param name="row"></param>
        //public void SaveToChangeBackup()
        //{
        //    DAO.SaveToChangeBackup(this);
        //}

        /// <summary>
        /// 
        /// </summary>
        public void MarkAsChanged()
        {
            //Element als verändert registrieren
            DAO.MarkAsChanged(this);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual int Delete()
        {
            int id = ID;
            Row[ELEMENTPROPERTY_ID] = ID * -1;

            //Hauptobject löschen
            //SaveToChangeBackup();
            MarkAsChanged();

            ElementTable.Rows.Remove(Row);
            ElementTable.AcceptChanges();

            return id;
        }

        #region BINDING

        /// <summary></summary>
        public event EventHandler<HEventArgs> OnPropertyChanged;
        /// <summary></summary>
        public event EventHandler<HEventArgs> OnAcceptChanges;
        /// <summary></summary>
        public event EventHandler<HEventArgs> OnRejectChanges;

        /// <summary></summary>
        private DataRow _bindingRow;
        /// <summary></summary>
        public DataRow BindingRow
        {
            get { return _bindingRow; }
            set { _bindingRow = value; }
        }
        /// <summary></summary>
        private DataTable _bindingTable;
        /// <summary></summary>
        public DataTable BindingTable
        {
            get
            {
                if (_bindingTable == null)
                {
                    _bindingTable = ElementTable.Clone();

                    if (Row.RowState != DataRowState.Detached)
                    {
                        //Änderung
                        _bindingTable.ImportRow(Row);
                        _bindingRow = _bindingTable.Rows[0];
                    }
                    else
                    {
                        //Neu Erstellung
                        _bindingRow = Row.Copy(_bindingTable);
                        _bindingTable.Rows.Add(_bindingRow);
                    }

                    _bindingTable.ColumnChanged += new DataColumnChangeEventHandler(_shadowTbl_ColumnChanged);

                }
                return _bindingTable;
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _shadowTbl_ColumnChanged(object sender, DataColumnChangeEventArgs e)
        {
            if (OnPropertyChanged != null)
                OnPropertyChanged(this, new HEventArgs(""));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="raiseEvent"></param>
        public virtual void AcceptChanges(bool raiseEvent)
        {
            if (IsNew)
            {
                _bindingRow[ElementBase.ELEMENTPROPERTY_ID] = GetNextId();

                if (!Row.RowState.Equals(DataRowState.Added))//Wegen Dates in Monthbalance
                {
                    ElementTable.Rows.Add(Row);
                }
            }

            foreach (DataRow row in _selectedRows)
            {
                row.Update(_bindingRow, Row);
            }

            //SaveToChangeBackup();
            MarkAsChanged();

            RaiseAcceptChangesEvent(raiseEvent, new HEventArgs(""));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="raiseEvent"></param>
        /// <param name="eventArg"></param>
        protected void RaiseAcceptChangesEvent(bool raiseEvent, HEventArgs eventArg)
        {
            if (raiseEvent && OnAcceptChanges != null)
                OnAcceptChanges(this, eventArg);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="raiseEvent"></param>
        public virtual void RejectChanges(bool raiseEvent)
        {
            _bindingRow.Update(Row);

            RaiseRejectChangesEvent(raiseEvent);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public List<string> GetChangedFields()
        {
            return _bindingRow.GetChangedFields(Row);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="raiseEvent"></param>
        protected void RaiseRejectChangesEvent(bool raiseEvent)
        {
            if (raiseEvent && OnRejectChanges != null)
                OnRejectChanges(this, new HEventArgs(""));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual bool ValueChanged()
        {
            bool result = Row.ValuesEquals(_bindingRow);// || IsNew;

            //UI-Validierung
            if (result)
            {
                result = result && (InputValidate().Count.Equals(0));
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected virtual List<string> InputValidate() { return new List<string>(); }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementType"></param>
        /// <param name="fieldName"></param>
        /// <param name="errorKey"></param>
        /// <returns></returns>
        protected string GetInputValidateMessage(Type elementType, string fieldName, string errorKey)
        {
            return string.Format("InputValidate.{0}.{1}.{2}", elementType.Name, fieldName, errorKey);
        }

        #endregion BINDING

        #region APPEARANCE METHODS

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual Color GetRowForeColor()
        {
            return Color.Black;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual Color GetRowBackgroundColor()
        {
            return Color.White;
        }

        #endregion APPEARANCE METHODS
    }
}
